﻿/**
 * Copyright (C) 2012-2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Binder;
using NGuice.Inject.Spi;
using NGuice.Inject.Internal.Util;
using System.Reflection;

namespace NGuice.Inject.Internal
{
    /// <summary>
    /// 绑定一个非常数的key
    /// </summary>
    public class BindingBuilder<T> : AbstractBindingBuilder<T>, IAnnotatedBindingBuilder<T>
    {
        public BindingBuilder(IBinder binder, IList<IElement> elements, object source, Key<T> key)
            : base(binder, elements, source, key)
        {
        }

        public void ToInstance(T instance)
        {
            CheckNotTargetted();

            // lookup the injection points, adding any errors to the binder's errors list
            ISet<InjectionPoint> injectionPoints;
            if (instance != null)
            {
                try
                {
                    injectionPoints = InjectionPoint.ForInstanceMethodsAndFields(instance.GetType());
                }
                catch (ConfigurationException e)
                {
                    CopyErrorsToBinder(e);
                    injectionPoints = e.GetPartialValue<ISet<InjectionPoint>>();
                }
            }
            else
            {
                binder.AddError(BINDING_TO_NULL);
                injectionPoints = ImmutableSet<InjectionPoint>.Of();
            }

            BindingImpl<T> super = Binding;
            Binding = new InstanceBindingImpl<T>(super.Source, super.Key, Scoping.EAGER_SINGLETON, injectionPoints, instance);
        }

        public IScopedBindingBuilder ToProvider(Key<IProvider<T>> providerKey)
        {
            Preconditions.CheckNotNull(providerKey, "providerKey");
            CheckNotTargetted();

            BindingImpl<T> super = Binding;
            Binding = new LinkedProviderBindingImpl<T>(super.Source, super.Key, super.Scoping, providerKey);
            return this;
        }

        public IScopedBindingBuilder ToProviderNoGenericKey(Key key)
        {
            Preconditions.CheckNotNull(key, "providerKey");
            Preconditions.CheckSubGenericParameter<IProvider<T>>(key);
            CheckNotTargetted();

            BindingImpl<T> super = Binding;
            Binding = new LinkedProviderBindingImpl<T>(super.Source, super.Key, super.Scoping, key);
            return this;
        }

        public IScopedBindingBuilder ToProvider(IProvider<T> provider)
        {
            Preconditions.CheckNotNull(provider, "provider");
            CheckNotTargetted();

            // lookup the injection points, adding any errors to the binder's errors list
            ISet<InjectionPoint> injectionPoints;
            try
            {
                injectionPoints = InjectionPoint.ForInstanceMethodsAndFields(provider.GetType());
            }
            catch (ConfigurationException e)
            {
                CopyErrorsToBinder(e);
                injectionPoints = e.GetPartialValue<ISet<InjectionPoint>>();
            }

            var super = Binding;
            Binding = new ProviderInstanceBindingImpl<T>(super.Source, super.Key, super.Scoping, injectionPoints, provider);
            return this;
        }

        public IScopedBindingBuilder ToConstructor(ConstructorInfo constructor)
        {
            return ToConstructor(constructor, constructor.DeclaringType);
        }

        public IScopedBindingBuilder ToConstructor(ConstructorInfo constructor, Type type)
        {
            Preconditions.CheckNotNull(constructor, "constructor");
            Preconditions.CheckNotNull(type, "type");
            CheckNotTargetted();

            BindingImpl<T> super = base.Binding;

            ISet<InjectionPoint> injectionPoints;
            try
            {
                injectionPoints = InjectionPoint.ForInstanceMethodsAndFields(type);
            }
            catch (ConfigurationException e)
            {
                CopyErrorsToBinder(e);
                injectionPoints = e.GetPartialValue<ISet<InjectionPoint>>();
            }
            return this;
        }

        public override string ToString()
        {
            return "BindingBuilder<" + Binding.Key.GetType().GetGenericTypeDefinition() + ">";
        }

        private void CopyErrorsToBinder(ConfigurationException e)
        {
            foreach (Message message in e.ErrorMessages)
            {
                binder.AddError(message);
            }
        }

        public ILinkedBindingBuilder<T> AnnotatedWith<E>() where E : Attribute
        {
            AnnotatedWithInternal(typeof(E));
            return this;
        }

        ILinkedBindingBuilder<T> IAnnotatedBindingBuilder<T>.AnnotatedWith(Type attributeType)
        {
            AnnotatedWithInternal(attributeType);
            return this;
        }

        ILinkedBindingBuilder<T> IAnnotatedBindingBuilder<T>.AnnotatedWith(Attribute attribute)
        {
            AnnotatedWithInternal(attribute);
            return this;
        }

        public IScopedBindingBuilder To()
        {
            return this.To(Key<T>.Get());
        }

        IScopedBindingBuilder ILinkedBindingBuilder<T>.To(Type implemention)
        {
            return To((Key<T>)Key<T>.GetForType(implemention));
        }


        public IScopedBindingBuilder To(Key<T> targetKey)
        {
            Preconditions.CheckNotNull(targetKey, "linkedKey");
            CheckNotTargetted();
            var super = Binding;
            Binding = new LinkedBindingImpl<T>(super.Source, super.Key, super.Scoping, targetKey);
            return this;
        }

        public IScopedBindingBuilder ToNoGenericKey(Key targetKey)
        {
            Preconditions.CheckNotNull(targetKey, "linkedKey");
            Preconditions.CheckSubGenericParameter<T>(targetKey);
            CheckNotTargetted();
            var super = Binding;
            Binding = new LinkedBindingImpl<T>(super.Source, super.Key, super.Scoping, targetKey);
            return this;
        }


        public void In<E>() where E : Attribute
        {
            this.In(typeof(E));
        }

    }
}
